package hu.debrecen.adastra.card.service;

import hu.debrecen.adastra.card.repository.BotAccountRepository;
import hu.debrecen.adastra.card.repository.domain.BotAccount;
import hu.debrecen.adastra.card.service.domain.Card;
import hu.debrecen.adastra.card.service.exceptions.FullGameException;
import hu.debrecen.adastra.card.service.impl.DummyBotPlayMode;
import hu.debrecen.adastra.card.web.domain.AbstractGameStateVO;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BotService {

	private static final Logger LOGGER = LoggerFactory.getLogger(BotService.class);

	private static final Random RANDOM = new Random();

	private final GameStoreService gameStoreService;

	private final CardGameFactory cardGameFactory;

	private final List<Bot> bots = new LinkedList<Bot>();

	private final Map<Bot, String> games = new HashMap<Bot, String>();

	@Inject
	public BotService(final GameStoreService gameStoreService, final BotAccountRepository botAccountRepository, final CardGameFactory cardGameFactory) {
		this.gameStoreService = gameStoreService;
		this.cardGameFactory = cardGameFactory;

		for (BotAccount botAccount : botAccountRepository.findAll()) {
			this.bots.add(new Bot(botAccount, new DummyBotPlayMode()));
		}

	}

	public void call() {

		for (Bot bot : this.bots) {

			// If game does not exist, it is going to be removed from running
			// games.
			if ((this.games.get(bot) != null) && !this.gameStoreService.exists(this.games.get(bot))) {
				this.games.remove(bot);
			}

			// If bot is playing
			if (this.games.containsKey(bot)) {

				AbstractGameStateVO gameState = this.getGameState(bot);

				// If game is finished, it is going to be removed
				if (gameState.isFinished()) {
					BotService.LOGGER.debug("Bot finished({}) this game({}).", bot.getAccount().getUsername(), this.games.get(bot));
					// TODO process taken cards
					this.games.remove(bot);
				} else {
					// Bot makes some call
					this.play(gameState, bot);
				}

			} else {

				// If bot does not play, it tries to find a game or create a new
				// one
				if ((BotService.RANDOM.nextInt() % 2) == 0) {
					this.findAvailableGame(bot);
				} else {
					this.createNewGame(bot);
				}

			}
		}

	}

	private void createNewGame(final Bot bot) {

		AbstractGame<?> cardGame = this.cardGameFactory.createDefaultCardGame(bot.getAccount());
		this.gameStoreService.put(cardGame);

		BotService.LOGGER.debug("Bot({}) create new game({}).", bot.getAccount(), cardGame.getGameId());
		this.games.put(bot, cardGame.getGameId());
	}

	private void play(final AbstractGameStateVO state, final Bot bot) {
		if (state.getCaller() && !state.getHand().isEmpty()) {
			Card card = bot.getPlayMode().evaluate(state);
			this.gameStoreService.getGame(this.games.get(bot)).call(bot.getAccount(), card.getType(), card.getSuit());
		}
	}

	private AbstractGameStateVO getGameState(final Bot bot) {
		return this.gameStoreService.getGame(this.games.get(bot)).getState(bot.getAccount());
	}

	private void findAvailableGame(final Bot bot) {

		Collection<AbstractGame<?>> availableGames = this.gameStoreService.getAvailableGames(bot.getAccount());

		for (AbstractGame<?> game : availableGames) {

			if (game.getOwner() instanceof BotAccount) {
				continue;
			}

			try {
				game.join(bot.getAccount());
				this.games.put(bot, game.getGameId());
				BotService.LOGGER.debug("Bot({}) joined to game({}).", bot.getAccount().getUsername(), game.getGameId());
			} catch (FullGameException e) {
				BotService.LOGGER.debug("Bot tried to join to game, but it is full.");
			}
		}
	}

	class Bot {

		private final BotAccount account;

		private final BotPlayMode playMode;

		public Bot(final BotAccount account, final BotPlayMode playMode) {
			this.account = account;
			this.playMode = playMode;
		}

		public BotAccount getAccount() {
			return this.account;
		}

		public BotPlayMode getPlayMode() {
			return this.playMode;
		}

	}

}
